#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "pub_lib.h"

/*
 * name: vector name
 * */
void print_vector(char* name, Vector3 v)
{
	if(name)
		printf("%s = ", name);
	else
		printf("vector(3,1) = ");

	printf("[ %.3f %.3f %.3f ]\n", v.x, v.y, v.z);
}

//quaternion: 4 * 1
void print_qm(char* name, Quaternion v)
{
	if(name)
		printf("%s = ", name);
	else
		printf("quaternion(4,1) = ");

	printf("[ %.3f %.3f %.3f %.3f]\n", v.x, v.y, v.z, v.w);

}

void print_mat(char* name, Matrix mat)
{
	int i, j;
	float *cur;
	if(name)
		printf("%s = \n", name);

	cur = (float*)&mat;
	printf("[\n");
	for(i = 0; i < 4; i++) {
		for( j = 0; j < 4; j++, cur++) {
			printf("%7.3f ", *cur);
		}
		printf("\n");
	}
	printf("]\n");
}

void print_matrix3(char* name, Matrix mat)
{
	int i, j;
	float *cur;
	if(name)
		printf("%s = \n", name);

	cur = (float*)&mat;
	printf("[\n");
	for(i = 0; i < 3; i++) {
		for( j = 0; j < 3; j++, cur++) {
			printf("%7.3f ", *cur);
		}
		cur++; //skip the last item
		printf("\n");
	}
	printf("]\n");
}

/*
 * a,b,c is apex of triangle
 * return 1 if clockwise
 * return 0 if anticlockwise
 * */
int is_clockwise(Vector3 a, Vector3 b, Vector3 c)
{
	int ret = 0;

	Vector3 ba = Vector3Subtract(b, a); //b - a
	Vector3 cb = Vector3Subtract(c, b); //c - b
	Vector3 result = Vector3CrossProduct(ba, cb);
	if (result.z > 0) {
		ret = 0;
		printf("triangle a - b - c is anticlockwise\n");
	} else {
		ret = 1;
		printf("triangle a - b - c is clockwise\n");
	}
	return ret;
}

/*
 * a,b,c is apex of triangle
 * return 1 if clockwise
 * return 0 if anticlockwise
 * */
int is_clockwiseQ(Quaternion v0, Quaternion v1, Quaternion v2)
{
	Vector3 a = {v0.x, v0.y, v0.z};
	Vector3 b = {v1.x, v1.y, v1.z};
	Vector3 c = {v2.x, v2.y, v2.z};

	return is_clockwise(a, b, c);
}

/* get the bounding box of triangle (a, b, c) */
BoundingBox calcBoundingBox(Vector3 a, Vector3 b, Vector3 c)
{
	BoundingBox box = { {0}, {0} };
	Vector3 min = Vector3Min(a, b);
	min = Vector3Min(min, c);

	Vector3 max = Vector3Max(a, b);
	max = Vector3Max(max, c);

	box.min = min;
    box.max = max;

	return box;
}

int get_point_index(int x, int y, int width, int height)
{
	return (y * width + x);
}

Color makeRGBA(Vector3 v)
{
	Color c = {v.x, v.y, v.z, 255};
	return c;
}

/* [0,1] RGB  to RGBA */
Color toColor(Vector3 color)
{
	Color ret = BLACK;
	if(color.x > 1.0f || color.y > 1.0f || color.z > 1.0f)
	{
		ret = WHITE;
	}else if(color.x < 0.0f || color.y < 0.0f || color.z < 0.0f) {
		ret = BLACK;
	}else {
		color = Vector3Scale(color, 255);
		ret = makeRGBA(color);
	}
	return ret;
}

/*
 * call me after BeginDrawing();
*/
void draw_model(Model* m, Vector3 eye, float fov, float scale)
{
	ClearBackground(WHITE);

	Camera camera = { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f },
		{0.0f, 1.0f, 0.0f }, 90.0f, 0 };
	camera.position = eye;
	camera.fovy = fov;

	Vector3 position = {0.0f, 0.0f, 0.0f };
	BeginMode3D(camera);
	//DrawModel(*m, position, scale, BLACK);
	DrawModelWires(*m, position, scale, BLACK);

	EndMode3D();
}

/*
 * call me after BeginDrawing();
 * background color is white
 * model color is black
*/
void draw_modelEx(Model* m, Vector3 eye, float fov, float scale, Vector3 axis, float angle)
{
	ClearBackground(WHITE);

	Camera camera = { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f },
		{0.0f, 1.0f, 0.0f }, 90.0f, 0 };
	camera.position = eye;
	camera.fovy = fov;

	Vector3 position = {0.0f, 0.0f, 0.0f };
	Vector3 s = {scale, scale, scale};

	BeginMode3D(camera);
	DrawModelWiresEx(*m, position, axis, angle, s, BLACK);
	//DrawModelEx(*m, position, axis, angle, s, BLACK);

	EndMode3D();
}


// Compute barycenter coordinates (u, v, w) for point p
// with respect to triangle (a, b, c)
// NOTE: Assumes P is on the plane of the triangle
Vector3 barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c)
{
	/* cross product version */
	Vector3 ba = Vector3Subtract(b, a);
    Vector3 ca = Vector3Subtract(c, a);
    Vector3 pa = Vector3Subtract(p, a);

	Vector3 A = Vector3CrossProduct(ba, ca);
	float denom = Vector3DotProduct(A, A);

	float val, u, v, w;
	Vector3 temp = Vector3CrossProduct(ba, pa);
	val = Vector3DotProduct(temp, A);
	w = val / denom;

	Vector3 cb = Vector3Subtract(c, b);
	Vector3 pb = Vector3Subtract(p, b);
	temp = Vector3CrossProduct(cb, pb);
	val = Vector3DotProduct(temp, A);
	u = val / denom;

	v = 1.0f - u - w;
	Vector3 result = {u, v, w};
    return result;
}

//init 3 vertices of a triangle
void init_triangle_points(float* val, void* dest, enum vertex_type type)
{
	if(VERTEX4 == type) {
		Vector4 v1 = {val[0], val[1], val[2], 1.0f};
		Vector4 v2 = {val[3], val[4], val[5], 1.0f};
		Vector4 v3 = {val[6], val[7], val[8], 1.0f};
		Vector4* dst = (Vector4*)dest;
		dst[0] = v1;
		dst[1] = v2;
		dst[2] = v3;
	} else if(VERTEX3 == type) {
		Vector3 v1 = {val[0], val[1], val[2]};
		Vector3 v2 = {val[3], val[4], val[5]};
		Vector3 v3 = {val[6], val[7], val[8]};
		Vector3* dst = (Vector3*)dest;
		dst[0] = v1;
		dst[1] = v2;
		dst[2] = v3;
	} else if(VERTEX2 == type) {
		Vector2 v1 = {val[0], val[1]};
		Vector2 v2 = {val[2], val[3]};
		Vector2 v3 = {val[4], val[5]};
		Vector2* dst = (Vector2*)dest;
		dst[0] = v1;
		dst[1] = v2;
		dst[2] = v3;
	}
}

//result[3] = ori[3] transform by matrix m
void trans_point(Vector3* result, Quaternion* ori, Matrix m)
{
	Quaternion new;
	int i;

	for( i = 0; i < 3; i++) {
		new = QuaternionTransform(ori[i], m);
		QuadToV3(result[i], new);
	}
}

//ensure v is in [low, high]
float clampf(const float low, const float high, const float v)
{
	float a = v;
	if (v > high)
		a = high;
	if (v < low)
		a = low;
	return a;
}

//x0 is nearest
bool solveQuadratic(const float a, const float b, const float c, float* x0, float* x1)
{
    float discr = b * b - 4 * a * c;
    if (discr < 0)
        return false;

    if (discr == 0)
        *x0 = *x1 = -0.5 * b / a;
    else
    {
        float q = (b > 0) ? -0.5 * (b + sqrt(discr)) : -0.5 * (b - sqrt(discr));
        *x0 = q / a;
        *x1 = c / q;
		if (*x0 > *x1)
			swapf(*x0, *x1);
    }
    return true;
}

//get min (x,y,z) of a,b
Vector3 get_min(Vector3 a, Vector3 b)
{
	if(b.x > a.x)
		b.x = a.x;
	if(b.y > a.y)
		b.y = a.y;
	if(b.z > a.z)
		b.z = a.z;
	return b;
}

//get max (x,y,z) of a,b
Vector3 get_max(Vector3 a, Vector3 b)
{
	if(b.x < a.x)
		b.x = a.x;
	if(b.y < a.y)
		b.y = a.y;
	if(b.z < a.z)
		b.z = a.z;
	return b;
}

void UpdateProgress(float progress)
{
	int barWidth = 70;

	printf("[");
	int pos = barWidth * progress;
	for (int i = 0; i < barWidth; ++i)
	{
		if (i < pos)
			printf("=");
		else if (i == pos)
			printf(">");
		else
			printf(" ");
	}
	printf("] %d\r", (int)(progress * 100.0));
	fflush(stdout);
}

